package avicit.bdp.dms.tdm.service;

import avicit.bdp.common.utils.ConfigUtils;
import avicit.bdp.common.utils.DateUtils;
import avicit.bdp.common.utils.IpUtils;
import avicit.bdp.common.utils.RequestWrapperUtils;
import avicit.bdp.common.utils.redis.RedisCacheHelper;
import avicit.bdp.core.util.json.JSONUtils;
import avicit.bdp.dms.dss.enums.ApiCallStatus;
import avicit.bdp.dms.oauth2.token.JWTOauthUtils;
import avicit.bdp.dms.tdm.dto.ApiSettingDTO;
import avicit.bdp.dms.tdm.dto.AppClientDTO;
import avicit.bdp.dms.tdm.dto.AssetLogDTO;
import avicit.bdp.dms.tdm.dto.BdpAssetDTO;
import avicit.bdp.dms.tdm.dto.BdpAssetMountDataServiceDTO;
import avicit.bdp.dms.tdm.dto.ColumnDTO;
import avicit.bdp.dms.tdm.service.adapter.DataServiceAdapter;
import avicit.bdp.dms.tdm.utils.ApplyServiceType;
import avicit.bdp.dms.tdm.utils.ReleaseStatus;
import avicit.bdp.dms.tdm.utils.ResourceType;
import avicit.bdp.dms.tdm.utils.TdmConstants;
import avicit.platform6.api.system.SysUserClient;
import avicit.platform6.commons.utils.ComUtil;
import avicit.platform6.core.context.ThreadContextHelper;
import avicit.platform6.core.rest.msg.ResponseMsg;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 * @金航数码科技有限责任公司
 * @作者：${tableInfo.classCreator}
 * @邮箱：${tableInfo.classMailbox}
 * @创建时间： 2023-12-13 11:03
 * @类说明：
 * @修改记录：
 */
@Service
public class AssetAPIService {

    private static final Logger logger = LoggerFactory.getLogger(AssetAPIService.class);

    @Autowired
    private AssetLogService assetLogService;
    @Autowired
    private BdpAssetMountDataServiceService assetMountDataServiceService;
    @Autowired
    private BdpAssetService assetService;
    @Autowired
    private AssetApplyService assetApplyService;
    @Autowired
    private DataServiceAdapter dataServiceAdapter;
    @Autowired
    private SysUserClient sysUserClient;
    @Autowired
    private ApiSettingService apiSettingService;
    @Autowired
    private RedisLimitService redisLimitService;
    @Autowired
    private AppClientService appClientService;

    ExecutorService executor = Executors.newFixedThreadPool(10);

    ExecutorService timeoutExecutor = Executors.newCachedThreadPool();

    /**
     * 对外API接口
     *
     * @param token
     * @param param
     * @param request
     * @param response
     */
    public void callApi(String token, Map<String, Object> param, HttpServletRequest request, HttpServletResponse response) {
        ResponseMsg<Map<String, Object>> responseMsg = new ResponseMsg<>();

        Map<String, Object> parameterMap = RequestWrapperUtils.getParameterMap(request);
        if (param != null) {
            parameterMap.putAll(param);
        }

        String ipAddress = IpUtils.getIpAddress(request);

        // 特殊uri处理，避免"//"开头
        final String requestUri = request.getRequestURI().replaceAll("//", "/");

        if (StringUtils.isEmpty(token)) {
            token = MapUtils.getString(parameterMap, "token");
        }

        // 记录调用日志
        AssetLogDTO logDTO = new AssetLogDTO();
        logDTO.setStartTime(new Date());
        logDTO.setServiceType(ApplyServiceType.API.getCode());
        try {

            boolean testApi = MapUtils.getBooleanValue(parameterMap, "testApi", false);

            logger.debug("远程调用接口，开始时间: {}, 客户端ip: {}, requestUri: {}, token:{}, testApi:{}, parameterMap:{}",
                    new Date(), ipAddress, requestUri, token, testApi, parameterMap);

            //校验资产是否存在
            BdpAssetMountDataServiceDTO mountDataService = validAsset(request, response, responseMsg, logDTO, requestUri, testApi);
            if (mountDataService == null) {
                return;
            }

            logDTO.setAssetId(mountDataService.getAssetId());
            //安全校验  黑名单限制  访问频率限制
            ApiSettingDTO apiSettingDTO = validSecurity(request, response, responseMsg, logDTO, ipAddress, mountDataService.getAssetId());
            if (apiSettingDTO == null) {
                return;
            }

            // testApi参数用于区别接口是否用于测试，如果是测试接口，则不必进行校验用户和权限信息
            if (testApi) {
                logDTO.setCallUserId(ThreadContextHelper.getUserId());
                logDTO.setCallUserName(ThreadContextHelper.getUserName());
            } else if (apiSettingDTO.getWhileList().contains(ipAddress)) {
                //在白名单里，不需要进行token校验
                logDTO.setCallUserId(apiSettingDTO.getCreatedBy());
            } else {
                //校验用户token
                boolean valid = validTokenAndUser(token, response, responseMsg, logDTO, mountDataService.getAssetId());
                if (!valid) {
                    return;
                }
            }
            //访问超时限制
            Integer timeout = apiSettingDTO.getTimeout();
            if (timeout == null) {
                timeout = ConfigUtils.getInstance().getInteger("asset.security.timeout", 10000);
            }
            Future future = timeoutExecutor.submit(() -> handleDataQuery(response, responseMsg, parameterMap, logDTO, mountDataService));
            future.get(apiSettingDTO.getTimeout(), TimeUnit.MILLISECONDS);
            //增加服务调用次数
            assetService.addCallCount(mountDataService.getAssetId());
        } catch (TimeoutException e) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.REQUEST_TIME_OUT, response, true);
            logger.error(e.getMessage(), e);
        } catch (Exception e) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.SYSTEM_ERROR, response, true);
            logger.error(e.getMessage(), e);
        }
        logger.info("远程调用接口，结束时间: {}", new Date());
    }

    /**
     * 接口安全配置校验
     *
     * @param request
     * @param response
     * @param responseMsg
     * @param logDTO
     * @param ipAddress
     * @param assetId
     * @return avicit.bdp.dms.tdm.dto.ApiSettingDTO
     */
    private ApiSettingDTO validSecurity(HttpServletRequest request, HttpServletResponse response, ResponseMsg<Map<String, Object>> responseMsg, AssetLogDTO logDTO, String ipAddress, String assetId) {
        String assetApiSettingStr = RedisCacheHelper.getInstance().get(TdmConstants.ASSET_API_SETTING + assetId);
        ApiSettingDTO apiSettingDTO;
        if (StringUtils.isBlank(assetApiSettingStr)) {
            apiSettingDTO = apiSettingService.queryByAssetId(assetId);
            if (apiSettingDTO != null) {
                RedisCacheHelper.getInstance().set(TdmConstants.ASSET_API_SETTING + assetId, JSONUtils.toJson(apiSettingDTO));
            }
        } else {
            apiSettingDTO = JSONUtils.parseObject(assetApiSettingStr, ApiSettingDTO.class);
        }
        //如果没有对单个API进行设置，则读取默认的数值
        if (apiSettingDTO == null) {
            apiSettingDTO = new ApiSettingDTO();
            apiSettingDTO.setFrequency(ConfigUtils.getInstance().getInteger("asset.security.frequency", 1000));
            apiSettingDTO.setTimeout(ConfigUtils.getInstance().getInteger("asset.security.timeout", 10000));
            apiSettingDTO.setBlackList("");
            apiSettingDTO.setWhileList("");
        }
        //黑名单限制
        String blackList = apiSettingDTO.getBlackList();
        if (blackList.contains(ipAddress)) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.IP_BLACK_LIST, response, true);
            return null;
        }

        //访问频率限制
        String key = assetId + ":" + ipAddress;
        Integer max = apiSettingDTO.getFrequency();
        if (max == null) {
            max = ConfigUtils.getInstance().getInteger("asset.security.frequency", 1000);
        }
        long timeout = 1L;
        TimeUnit timeUnit = TimeUnit.SECONDS;
        Boolean limited = redisLimitService.checkRestricted(key, max, timeout, timeUnit);
        if (limited) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.IP_BLACK_LIST, response, true);
            return null;
        }

        return apiSettingDTO;
    }

    /**
     * 资产存在和发布校验
     *
     * @param request
     * @param response
     * @param responseMsg
     * @param logDTO
     * @param requestUri
     * @param testApi
     * @return avicit.bdp.dms.tdm.dto.BdpAssetMountDataServiceDTO
     */
    private BdpAssetMountDataServiceDTO validAsset(HttpServletRequest request, HttpServletResponse response,
                                                   ResponseMsg<Map<String, Object>> responseMsg,
                                                   AssetLogDTO logDTO, String requestUri, boolean testApi) {
        // 通过分享uri查询资产信息,如果资产不存在，或者请求方式不一致，则提示用户 【调用接口不存在】
        String assetDataServiceStr = RedisCacheHelper.getInstance().get(TdmConstants.ASSET_DATA_SERVICE_REQUEST_URI + requestUri);
        BdpAssetMountDataServiceDTO mountDataService;
        if (StringUtils.isBlank(assetDataServiceStr)) {
            mountDataService = assetMountDataServiceService.getByRequestUri(requestUri);
            if (mountDataService != null) {
                RedisCacheHelper.getInstance().set(TdmConstants.ASSET_DATA_SERVICE_REQUEST_URI + requestUri, JSONUtils.toJson(mountDataService));
            }

        } else {
            mountDataService = JSONUtils.parseObject(assetDataServiceStr, BdpAssetMountDataServiceDTO.class);
        }

        if (mountDataService == null) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.CALLING_INTERFACE_NOT_FOUND, response, true);
            return null;
        }

        if (!request.getMethod().equalsIgnoreCase(mountDataService.getRequestMethod())) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.CALLING_INTERFACE_METHOD_ERROR, response, true);
            return null;
        }

        //判断资产是否上下线
        String assetStr = RedisCacheHelper.getInstance().get(TdmConstants.ASSET_DATA + mountDataService.getAssetId());
        BdpAssetDTO assetDTO;
        if (StringUtils.isBlank(assetStr)) {
            assetDTO = assetService.selectByPrimaryKey(mountDataService.getAssetId());
            if (assetDTO != null) {
                RedisCacheHelper.getInstance().set(TdmConstants.ASSET_DATA + mountDataService.getAssetId(), JSONUtils.toJson(assetDTO));
            }
        } else {
            assetDTO = JSONUtils.parseObject(assetStr, BdpAssetDTO.class);
        }

        if (assetDTO == null) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.CALLING_INTERFACE_NOT_FOUND, response, true);
            return null;
        }

        // 资产如果已下线，提示用户【调用接口已下线】
        if (!testApi && ReleaseStatus.PUBLISHED.ordinal() != assetDTO.getReleaseStatus()) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.CALLING_INTERFACE_OFFLINE, response, true);
            return null;
        }
        // 资产类型为数据服务
        if (ResourceType.DATA_SERVICE.getCode() != Integer.parseInt(assetDTO.getAssetType())) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.CALLING_INTERFACE_NOT_FOUND, response, true);
            return null;
        }

        // 校验资产有效期
        if (assetDTO.getEffectiveTimeType() == 1) {
            Date validStartDate = assetDTO.getEffectiveTimeStart();
            if (validStartDate != null && !DateUtils.compare(new Date(), validStartDate)) {
                recordCallStatus(responseMsg, logDTO, ApiCallStatus.DATE_EXPIRED, response, true);
                return null;
            }

            Date validEndDate = assetDTO.getEffectiveTimeEnd();
            if (validEndDate != null && DateUtils.compare(new Date(), validEndDate)) {
                recordCallStatus(responseMsg, logDTO, ApiCallStatus.DATE_EXPIRED, response, true);
                return null;
            }
        }

        return mountDataService;
    }

    /**
     * 接口数据查询执行
     *
     * @param response
     * @param responseMsg
     * @param parameterMap
     * @param logDTO
     * @param mountDataService
     * @return void
     */
    private void handleDataQuery(HttpServletResponse response, ResponseMsg<Map<String, Object>> responseMsg,
                                 Map<String, Object> parameterMap, AssetLogDTO logDTO, BdpAssetMountDataServiceDTO mountDataService) {
        // 校验翻页参数
        if (!parameterMap.containsKey("pageNo") || !parameterMap.containsKey("pageSize")) {
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.INVALID_PARAM, response, true);
            return;
        }

        int pageNo = MapUtils.getIntValue(parameterMap, "pageNo", 1);
        int pageSize = MapUtils.getIntValue(parameterMap, "pageSize", 10);

        try {
            // 转换允许输入的参数
            List<ColumnDTO> columnInfoDtoList = JSONUtils.toList(mountDataService.getColumnInfo(), ColumnDTO.class);
            Map<String, Object> paramMap = new HashMap<>();
            for (ColumnDTO columnInfoDto : columnInfoDtoList) {
                if (columnInfoDto.getIsParam() != null && columnInfoDto.getIsParam()) {
                    if (parameterMap.containsKey(columnInfoDto.getName())) {
                        paramMap.put(columnInfoDto.getName(), parameterMap.remove(columnInfoDto.getName()));
                    }
                }
            }

            Map<String, Object> dataMap = dataServiceAdapter.getDataListFromDB(mountDataService, pageNo, pageSize, paramMap);
            if (MapUtils.isNotEmpty(dataMap)) {
                dataMap.remove("columnList");
                dataMap.remove("assetMountData");
                dataMap.remove("modelInfo");
                dataMap.put("result", dataMap.get("resultList"));
                dataMap.remove("resultList");

                Map<String, Object> pageMap = (Map) dataMap.get("pageParameter");
                pageMap.put("pageNo", pageMap.get("page"));
                pageMap.put("pageSize", pageMap.get("rows"));
                pageMap.remove("page");
                pageMap.remove("rows");
            }
            responseMsg.setResponseBody(dataMap);
            if (MapUtils.isNotEmpty(dataMap)) {
                List<Map<String, Object>> resultList = (List<Map<String, Object>>) dataMap.get("result");
                logDTO.setCallResult("查询数据" + resultList.size() + "条");
            }
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.SUCCESS, response, true);
        } catch (Exception e) {
            logger.error("call error", e);
            logDTO.setCallResult(e.getMessage());
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.SYSTEM_ERROR, response, true);
        }
    }

    /**
     * 用户请求身份校验
     *
     * @param token
     * @param response
     * @param responseMsg
     * @param logDTO
     * @param assetId
     * @return boolean
     */
    private boolean validTokenAndUser(String token, HttpServletResponse response, ResponseMsg<Map<String, Object>> responseMsg,
                                      AssetLogDTO logDTO, String assetId) {
        try {
            if (StringUtils.isBlank(token)) {
                recordCallStatus(responseMsg, logDTO, ApiCallStatus.TOKEN_INVALID, response, true);
                return false;
            }
            //签名方式，请求token后，在缓存中放入对应的appKey
            //简易方式，token为clientId，默认是20位
            String clientId;
            if (token.length() != 20) {
                //表示是签名方式生成的token
                try {
                    //token是否过期
                    JWTOauthUtils.verifyToken(token);
                } catch (Exception e) {
                    recordCallStatus(responseMsg, logDTO, ApiCallStatus.TOKEN_INVALID, response, true);
                    return false;
                }
                DecodedJWT jwt = JWTOauthUtils.decodeToken(token);
                Claim claim = jwt.getClaim("clientId");
                clientId = claim == null ? "" : claim.asString();

            } else {
                //可能是简易方式的appKey，也可能是错误的key或token, 默认key是20位
                clientId = token;
            }

            String appKeyStr = RedisCacheHelper.getInstance().get(TdmConstants.ASSET_APP_CLIENT + clientId);
            AppClientDTO appClientDTO;
            if (StringUtils.isBlank(appKeyStr)) {
                appClientDTO = appClientService.selectByPrimaryKey(clientId);
                if (appClientDTO == null) {
                    recordCallStatus(responseMsg, logDTO, ApiCallStatus.TOKEN_INVALID, response, true);
                    return false;
                }
                RedisCacheHelper.getInstance().set(TdmConstants.ASSET_APP_CLIENT + clientId, JSONUtils.toJson(appClientDTO));

            } else {
                appClientDTO = JSONUtils.parseObject(appKeyStr, AppClientDTO.class);
            }

            logDTO.setCallUserId(appClientDTO.getCreatedBy());
            logDTO.setCallUserName(appClientDTO.getClientName());

            // 如果没有申请或者未通过审核，提示用户【您没有调用该接口的权限】
            String applyStr = RedisCacheHelper.getInstance().get(TdmConstants.ASSET_APPLY_CLIENTID + clientId);
            if (StringUtils.isBlank(applyStr)) {
                Long applyState = assetApplyService.getApplyStateByUserId(appClientDTO.getCreatedBy(), assetId, appClientDTO.getId());
                if (applyState == null || applyState == 0) {
                    recordCallStatus(responseMsg, logDTO, ApiCallStatus.USER_UNSUBSCRIBE, response, true);
                    return false;
                }
                RedisCacheHelper.getInstance().set(TdmConstants.ASSET_APPLY_CLIENTID + clientId, clientId);
            }


        } catch (Exception e) {
            logger.error("call error", e);
            logDTO.setCallResult(e.getMessage());
            recordCallStatus(responseMsg, logDTO, ApiCallStatus.NO_PERMISSION_CALL_INTERFACE, response, true);
            return false;
        }

        return true;
    }

    private void recordCallStatus(ResponseMsg<Map<String, Object>> responseMsg, AssetLogDTO logDTO, ApiCallStatus callStatus,
                                  HttpServletResponse response, boolean isReturn) {
        responseMsg.setRetCode(String.valueOf(callStatus.getCode()));

        logDTO.setId(ComUtil.getId());
        logDTO.setEndTime(new Date());
        logDTO.setCallStatus(callStatus.getCode() == 200 ? 1 : 0);
        if (StringUtils.isBlank(logDTO.getCallResult())) {
            logDTO.setCallResult(callStatus.getDescription());
        }

        responseMsg.setErrorDesc(logDTO.getCallResult());

        if (StringUtils.isNotEmpty(logDTO.getAssetId()) && StringUtils.isNotEmpty(logDTO.getCallUserId())) {
            AssetAPIService.LogOperate logOperate = new AssetAPIService.LogOperate(logDTO);
            executor.submit(logOperate);
        }

        if (isReturn) {
            JSONUtils.ajaxJson(JSONObject.toJSONString(responseMsg, JSONWriter.Feature.WriteMapNullValue), response);
        }
    }

    public class LogOperate implements Runnable {

        private final AssetLogDTO log;

        LogOperate(AssetLogDTO log) {
            this.log = log;
        }

        @Override
        public void run() {
            if (log != null) {
                try {
                    assetLogService.insert(log);
                } catch (Exception e) {
                    logger.error("异步插入日志异常", e);
                }
            }
        }
    }

}